﻿/*******************************************************************************
Copyright (c) 2011-2013, PKStudio
Copyright (c) 2011,      Zynga Inc.
Copyright (c) 2008-2010, Ricardo Quesada

http://pkstudio.org

Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
of the Software, and to permit persons to whom the Software is furnished to do
so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*******************************************************************************/

using System;

namespace PKEngine
{
	/// <summary>
	/// Base class of PKAction.
	/// </summary>
	public class PKAction
	{
		#region Public Constant
		public const int PKActionInvalidTag = -1;
		#endregion

		#region Public Properties
		/// <summary>
		/// The target. Action will modify the target properties.
		/// The target will be set with the 'StartWithTarget' method.
		/// When the 'Stop' method is called, target will be set to null.
		/// </summary>
		public PKNode Target { get; private set; }

		/// <summary>
		/// The original target, since target can be null.
		/// Is the target that were used to run the action.
		/// </summary>
		public PKNode OriginalTarget { get; private set; }

		/// <summary>
		/// Tag of action.
		/// </summary>
		public int Tag { get; set; }

		/// <summary>
		/// Return true if the action has finished.
		/// </summary>
		public virtual bool IsDone
		{
			get
			{
				return true;
			}
		}
		#endregion

		#region Initialization
		public PKAction()
		{
			this.OriginalTarget = null;
			this.Target = null;
			this.Tag = PKActionInvalidTag;
		}
		#endregion

		#region PublicMethods
		/// <summary>
		/// Called before the action start. It will also set the target.
		/// </summary>
		/// <param name="target">The target</param>
		public virtual void StartWithTarget(PKNode target)
		{
			OriginalTarget = target;
			Target = target;
		}

		/// <summary>
		/// Called after the action has finished. It will set the target to null.
		/// </summary>
		/// <remarks>You should never call "[action stop]" manually.</remarks>
		/// <remarks>Instead, use: "[target stopAction:action]"</remarks>
		public virtual void Stop()
		{
			Target = null;
		}

		/// <summary>
		/// Called every frame with it's delta time.
		/// DON'T override unless you know what you are doing.
		/// </summary>
		/// <param name="dt">Delta time</param>
		public virtual void Step(float dt)
		{
			// Override me.
		}

		/// <summary>
		/// Called once per frame.
		/// </summary>
		/// <param name="time">A value between 0 and 1</param>
		/// <remarks>0 means that the action just started</remarks>
		/// <remarks>0.5 means that the action is in the middle</remarks>
		/// <remarks>1 means that the action is over</remarks>
		public virtual void Update(float time)
		{
			// Override me.
		}
		#endregion
	}

	/// <summary>
	/// Base class actions that do have a finite time duration.
	/// </summary>
	public class PKFiniteTimeAction : PKAction
	{
		#region Public Properties
		/// <summary>
		/// Duration in seconds.
		/// </summary>
		public float Duration { get; set; }
		#endregion

		#region Public Methods
		/// <summary>
		/// Gets a reversed action.
		/// </summary>
		/// <returns>Reversed action</returns>
		public virtual PKFiniteTimeAction Reverse()
		{
			// Override me.
			return null;
		}

		/// <summary>
		/// Gets a copy of the action.
		/// </summary>
		/// <returns>Copy of the action</returns>
		/// <remarks>DO NOT use an action twice. Please make a copy for the oringinal action.</remarks>
		/// <remarks>Tag property will not be copied.</remarks>
		public virtual PKFiniteTimeAction Copy()
		{
			// Override me.
			return null;
		}
		#endregion
	}

	/// <summary>
	/// Repeats an action for ever.
	/// <remarks>To repeat the an action for a limited number of times use the Repeat action.</remarks>
	/// <remarks>This action can't be Sequenceable because it is not an ActionInterval.</remarks>
	/// </summary>
	public class PKRepeatForever : PKAction
	{
		#region Public Properties
		/// <summary>
		/// Inner action.
		/// </summary>
		public PKActionInterval InnerAction { get; private set; }

		/// <summary>
		/// Return true if the action has finished.
		/// </summary>
		public override bool IsDone
		{
			get
			{
				return false;
			}
		}
		#endregion

		#region Initialization
		public PKRepeatForever(PKActionInterval action)
			: base()
		{
			this.InnerAction = action;
		}
		#endregion

		#region Public Methods
		public override void StartWithTarget(PKNode target)
		{
			base.StartWithTarget(target);
			InnerAction.StartWithTarget(target);
		}

		public override void Stop()
		{
			InnerAction.Stop();
			base.Stop();
		}

		public override void Step(float dt)
		{
			InnerAction.Step(dt);
			if (InnerAction.IsDone)
			{
				float diff = InnerAction.Elapsed - InnerAction.Duration;
				InnerAction.StartWithTarget(Target);
				InnerAction.Step(0);
				InnerAction.Step(diff);
			}
		}

		public PKRepeatForever Copy()
		{
			return new PKRepeatForever(InnerAction.Copy() as PKActionInterval);
		}

		public PKRepeatForever Reverse()
		{
			return new PKRepeatForever(InnerAction.Reverse() as PKActionInterval);
		}
		#endregion
	}

	/// <summary>
	/// Changes the speed of an action, making it take longer (speed>1) or less (speed<1) time.
	/// <remarks>This action can't be Sequenceable because it is not an ActionInterval.</remarks>
	/// </summary>
	public class PKSpeed : PKAction
	{
		#region Public Properties
		/// <summary>
		/// Inner action.
		/// </summary>
		public PKActionInterval InnerAction { get; private set; }

		/// <summary>
		/// Alter the speed of the inner function in runtime.
		/// </summary>
		public float Speed { get; set; }

		/// <summary>
		/// Return true if the action has finished.
		/// </summary>
		public override bool IsDone
		{
			get
			{
				return InnerAction.IsDone;
			}
		}
		#endregion

		#region Initialization
		public PKSpeed(PKActionInterval action, float speed)
			: base()
		{
			this.InnerAction = action;
			this.Speed = speed;
		}
		#endregion

		#region Public Methods
		public override void StartWithTarget(PKNode target)
		{
			base.StartWithTarget(target);
			InnerAction.StartWithTarget(target);
		}

		public override void Stop()
		{
			InnerAction.Stop();
			base.Stop();
		}

		public override void Step(float dt)
		{
			InnerAction.Step(dt * Speed);
		}

		public PKSpeed Copy()
		{
			return new PKSpeed(InnerAction.Copy() as PKActionInterval, Speed);
		}

		public PKSpeed Reverse()
		{
			return new PKSpeed(InnerAction.Reverse() as PKActionInterval, Speed);
		}
		#endregion
	}
}
